﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Text;
using CRMDisco;
using System.Web.Services.Protocols;
using Microsoft.Practices.EnterpriseLibrary.Logging;
using Crm.MetaDataService;
using Crm.CrmService;

namespace Crm.Helper
{
    public enum TypeOfCRMRequest
    {
        Update,
        Create,
        Execute
    }

    public class CRMHelper
    {
        #region "Private members"

        private CrmService.CrmService _crmService = null;
        private MetadataService _crmMetaDataService = null;
        private organization _organization = null;
        private Dictionary<string, Dictionary<string, AttributeMetadata>> _metaData = null;

        private string _crmOrganization = string.Empty;
        private string _crmUsername = string.Empty;
        private string _crmPassword = string.Empty;
        private string _crmDomain = string.Empty;
        private string _serviceUrl = string.Empty;
        private string _metaServiceUrl = string.Empty;

        private int _errorRetryCount = 0;

        #endregion

        public CRMHelper(string Organization, string Username, string Password, string Domain, string ServiceUrl, string MetaServiceUrl)
        {
            _crmOrganization = Organization;
            _crmUsername = Username;
            _crmPassword = Password;
            _crmDomain = Domain;
            _serviceUrl = ServiceUrl;
            _metaServiceUrl = MetaServiceUrl;
        }

        #region "Properties"

        Dictionary<string, Dictionary<string, AttributeMetadata>> MetaData
        {
            get
            {
                if (_metaData == null)
                {
                    _metaData = new Dictionary<string, Dictionary<string, AttributeMetadata>>();
                    BuildEntityMetadata();
                }
                return _metaData;
            }
            set
            {
                _metaData = value;
            }
        }

        private void BuildEntityMetadata()
        {
            string functionName = GetType().ToString() + " BuildEntityMetadata: ";
            Logger.Write(functionName + "Enter", "Trace", 3);
            try
            {
                RetrieveAllEntitiesRequest request = new RetrieveAllEntitiesRequest()
                {
                    MetadataItems = MetadataItems.IncludeAttributes,
                    RetrieveAsIfPublished = false
                };
                RetrieveAllEntitiesResponse response = (RetrieveAllEntitiesResponse)CRMMetaDataService.Execute(request);
                CrmMetadata[] allEntities = response.CrmMetadata;

                EntityMetadata[] entities = new EntityMetadata[allEntities.Length];
                for (int i = 0; i < allEntities.Length; i++)
                {
                    entities[i] = (EntityMetadata)allEntities[i];
                    Dictionary<string, AttributeMetadata> attributes = new Dictionary<string, AttributeMetadata>();
                    foreach (AttributeMetadata attributeMetadata in entities[i].Attributes)
                        attributes.Add(attributeMetadata.LogicalName, attributeMetadata);
                    _metaData.Add(entities[i].LogicalName, attributes);
                }
            }
            catch (Exception ex)
            {
                Logger.Write(functionName + "Error: " + ex.ToString(), "Error", 5, 999, TraceEventType.Error);
                throw;
            }
            finally
            {
                Logger.Write(functionName + "Exit", "Trace", 3);
            }
        }

        public CrmService.CrmService CRMService
        {
            get
            {
                string functionName = GetType().ToString() + " CRMService: ";
                if (_crmService == null)
                {
                    _errorRetryCount = 0;
                RetryCreate:
                    try
                    {
                        Logger.Write(functionName + "Creating CrmService", "Trace", 1);
                        _crmService = new CrmService.CrmService(_serviceUrl);
                        Logger.Write(functionName + "Creating CrmAuthenticationToken ", "Trace", 1);
                        Crm.CrmService.CrmAuthenticationToken token = new Crm.CrmService.CrmAuthenticationToken
                        {
                            AuthenticationType = 0,
                            OrganizationName = _crmOrganization
                        };
                        Logger.Write(functionName + "Setting PreAuthenticate", "Trace", 1);
                        _crmService.PreAuthenticate = true;
                        Logger.Write(functionName + "Setting NetworkCredential", "Trace", 1);
                        _crmService.Credentials = new NetworkCredential(_crmUsername, _crmPassword, _crmDomain);
                        Logger.Write(functionName + "Setting CrmAuthenticationTokenValue", "Trace", 1);
                        _crmService.CrmAuthenticationTokenValue = token;
                        Logger.Write(functionName + "CrmService Created", "Trace", 1);
                    }
                    catch (Exception ex)
                    {
                        if (ErrorIsRecoverable(ex))
                            goto RetryCreate;
                        else
                            throw;
                    }
                    finally
                    {
                        Logger.Write(functionName + "Exit", "Trace", 3);
                    }
                }
                
                return _crmService;
            }
            set
            {
                _crmService = value;
            }
        }

        public MetadataService CRMMetaDataService
        {
            get
            {
                string functionName = GetType().ToString() + " CRMMetaDataService: ";
                if (_crmMetaDataService == null)
                {
                    _errorRetryCount = 0;
                RetryCreate:
                    try
                    {
                        Logger.Write(functionName + "Creating MetadataService", "Trace", 1);
                        _crmMetaDataService = new MetadataService(_metaServiceUrl);
                        Logger.Write(functionName + "Creating CrmAuthenticationToken ", "Trace", 1);
                        Crm.MetaDataService.CrmAuthenticationToken token = new Crm.MetaDataService.CrmAuthenticationToken
                        {
                            AuthenticationType = 0,
                            OrganizationName = _crmOrganization
                        };
                        Logger.Write(functionName + "Setting PreAuthenticate", "Trace", 1);
                        _crmMetaDataService.PreAuthenticate = true;
                        Logger.Write(functionName + "Setting NetworkCredential", "Trace", 1);
                        _crmMetaDataService.Credentials = new NetworkCredential(_crmUsername, _crmPassword, _crmDomain);
                        Logger.Write(functionName + "Setting URL", "Trace", 1);
                        _crmMetaDataService.Url = _metaServiceUrl;
                        Logger.Write(functionName + "Setting CrmAuthenticationTokenValue", "Trace", 1);
                        _crmMetaDataService.CrmAuthenticationTokenValue = token;
                        Logger.Write(functionName + "MetadataService Created", "Trace", 1);
                    }
                    catch (Exception ex)
                    {
                        if (ErrorIsRecoverable(ex))
                            goto RetryCreate;
                        else
                            throw;
                    }
                }
                return _crmMetaDataService;
            }
        }

        public organization Organization
        {
            get
            {
                if (_organization == null)
                {
                    WhoAmIRequest whoAmIReq = new WhoAmIRequest();
                    WhoAmIResponse whoAmIRes = (WhoAmIResponse)CallCRMExecuteMethod(whoAmIReq);
                    _organization = (organization)GetSingleData(EntityName.organization.ToString(), whoAmIRes.OrganizationId.ToString());
                }
                return _organization;
            }
        }

        public AttributeMetadata GetAttribute(string EntityName, string Name)
        {
            string functionName = GetType().ToString() + " GetAttribute: ";
            Logger.Write(functionName + "Enter", "Trace", 3);
            Logger.Write(functionName + "Parameter: Entity: " + EntityName, "Trace", 1);
            Logger.Write(functionName + "Parameter: Name: " + Name, "Trace", 1);
            try
            {
                Dictionary<string, AttributeMetadata> attributes = MetaData[EntityName];
                foreach (string attributeName in attributes.Keys)
                {
                    if (attributes[attributeName].LogicalName.ToLower() == Name.ToLower())
                        return attributes[attributeName];
                }
                return null;
            }
            catch (SoapException sEx)
            {
                if (sEx.Detail != null)
                    Logger.Write(functionName + "Error: " + sEx.Detail.InnerText.ToString(), "Error", 5, 999, TraceEventType.Error);
                else
                    Logger.Write(functionName + "Error: " + sEx.ToString(), "Error", 5, 999, TraceEventType.Error);
                throw;
            }
            catch (Exception ex)
            {
                Logger.Write(functionName + "Error: " + ex.ToString(), "Error", 5, 999, TraceEventType.Error);
                throw;
            }
            finally
            {
                Logger.Write(functionName + "Exit", "Trace", 3);
            }
        }

        #endregion

        #region "Sending of mail from CRM"

        public void SendCRMMail(string Subject, string Body, string SendToType, string SendToId, string SendFromType, string SendFromID, bool IsAutoReply)
        {
            SendCRMMail(Subject, Body, SendToType, SendToId, SendFromType, SendFromID, string.Empty, string.Empty, IsAutoReply);
        }

        public void SendCRMMail(string Subject, string Body, string SendToType, string SendToId, string SendFromType, string SendFromID, string RegardingType, string RegardingId, bool IsAutoReply)
        {
            string functionName = GetType().ToString() + " SendCRMMail: ";
            Logger.Write(functionName + "Enter", "Trace", 3);
            Logger.Write(functionName + "Parameter: Subject:      " + Subject, "Trace", 1);
            Logger.Write(functionName + "Parameter: SendToType:   " + SendToType, "Trace", 1);
            Logger.Write(functionName + "Parameter: SendToId:     " + SendToId, "Trace", 1);
            Logger.Write(functionName + "Parameter: SendFromType: " + SendFromType, "Trace", 1);
            Logger.Write(functionName + "Parameter: SendFromID:   " + SendFromID, "Trace", 1);
            Logger.Write(functionName + "Parameter: RegardingType:" + RegardingType, "Trace", 1);
            Logger.Write(functionName + "Parameter: RegardingId:  " + RegardingId, "Trace", 1);
            Logger.Write(functionName + "Parameter: IsAutoReply:  " + IsAutoReply.ToString(), "Trace", 1);
            try
            {
                Guid emailId = CreateEmail(Subject, Body, SendToType, SendToId, SendFromType, SendFromID, RegardingType, RegardingId, IsAutoReply);
                SendEmail(Subject, emailId);
            }
            catch (SoapException sEx)
            {
                if (sEx.Detail != null)
                    Logger.Write(functionName + "Error: " + sEx.Detail.InnerText.ToString(), "Error", 5, 999, TraceEventType.Error);
                else
                    Logger.Write(functionName + "Error: " + sEx.ToString(), "Error", 5, 999, TraceEventType.Error);
                throw;
            }
            catch (Exception ex)
            {
                Logger.Write(functionName + "Error: " + ex.ToString(), "Error", 5, 999, TraceEventType.Error);
                throw;
            }
            finally
            {
                Logger.Write(functionName + "Exit", "Trace", 3);
            }
        }

        private Guid CreateEmail(string Subject, string Body, string SendToType, string SendToId, string SendFromType, string SendFromID, string RegardingType, string RegardingId, bool IsAutoReply)
        {
            string functionName = GetType().ToString() + " CreateEmail: ";
            Logger.Write(functionName + "Enter", "Trace", 3);
            try
            {
                activityparty from = new activityparty();
                from.partyid = new Lookup();
                from.partyid.type = SendFromType;
                from.partyid.Value = new Guid(SendFromID);

                activityparty to = new activityparty();
                to.partyid = new Lookup();
                to.partyid.type = SendToType;
                to.partyid.Value = new Guid(SendToId);

                Crm.CrmService.CrmBoolean autoReply = new Crm.CrmService.CrmBoolean();
                autoReply.Value = IsAutoReply;
                autoReply.name = "new_autocasereply";

                //Non-standard customization - Add field to CRM & uncomment to use
                email email = new email();
                email.subject = Subject;
                email.description = Body;
                //email.new_autocasereply = autoReply;
                email.from = new activityparty[] { from };
                email.to = new activityparty[] { to };

                if (!string.IsNullOrEmpty(RegardingId) && !string.IsNullOrEmpty(RegardingType))
                {
                    email.regardingobjectid = new Lookup();
                    email.regardingobjectid.type = RegardingType;
                    email.regardingobjectid.Value = new Guid(RegardingId);
                }
                Guid emailId = CallCRMMethod(email, TypeOfCRMRequest.Create);
                return emailId;
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                Logger.Write(functionName + "Exit", "Trace", 3);
            }
        }

        private void SendEmail(string Subject, Guid emailId)
        {
            string functionName = GetType().ToString() + " SendEmail: ";
            Logger.Write(functionName + "Enter", "Trace", 3);
            try
            {
                GetTrackingTokenEmailRequest trackingRequest = new GetTrackingTokenEmailRequest();
                GetTrackingTokenEmailResponse trackingResponse = new GetTrackingTokenEmailResponse();
                SendEmailRequest sendEmailRequest = new SendEmailRequest();
                trackingRequest.Subject = Subject;
                trackingResponse = (GetTrackingTokenEmailResponse)CallCRMExecuteMethod(trackingRequest);
                sendEmailRequest.EmailId = emailId;
                sendEmailRequest.IssueSend = true;
                sendEmailRequest.TrackingToken = trackingResponse.TrackingToken;
                CallCRMExecuteMethod(sendEmailRequest);
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                Logger.Write(functionName + "Exit", "Trace", 3);
            }
        }

        #endregion

        public DynamicEntity GetDynamicEntity(string EntityName, string EntityId)
        {
            string functionName = GetType().ToString() + " GetDynamicEntity: ";
            Logger.Write(functionName + "Enter", "Trace", 3);
            Logger.Write(functionName + "Parameter: EntityName: " + EntityName, "Trace", 1);
            Logger.Write(functionName + "Parameter: EntityId  : " + EntityId, "Trace", 1);
            try
            {
                TargetRetrieveDynamic retrieveTarget = new TargetRetrieveDynamic();
                RetrieveRequest retrieveRequest = new RetrieveRequest();
                RetrieveResponse retrieveResponse;
                retrieveTarget.EntityName = EntityName;
                retrieveTarget.EntityId = new Guid(EntityId);
                retrieveRequest.Target = retrieveTarget;
                retrieveRequest.ColumnSet = new AllColumns();
                retrieveRequest.ReturnDynamicEntities = true;
                retrieveResponse = (RetrieveResponse)CallCRMExecuteMethod(retrieveRequest);
                if (retrieveResponse.BusinessEntity != null)
                    return (DynamicEntity)retrieveResponse.BusinessEntity;
                else
                    return null;
            }
            catch (SoapException sEx)
            {
                if (sEx.Detail != null)
                    Logger.Write(functionName + "Error: " + sEx.Detail.InnerText.ToString(), "Error", 5, 999, TraceEventType.Error);
                else
                    Logger.Write(functionName + "Error: " + sEx.ToString(), "Error", 5, 999, TraceEventType.Error);
                throw;
            }
            catch (Exception ex)
            {
                Logger.Write(functionName + "Error: " + ex.ToString(), "Error", 5, 999, TraceEventType.Error);
                throw;
            }
            finally
            {
                Logger.Write(functionName + "Exit", "Trace", 3);
            }
        }

        public BusinessEntity GetSingleData(string EntityName, string EntityId)
        {
            return GetSingleData(EntityName, EntityId, new string[] { });
        }

        public BusinessEntity GetSingleData(string EntityName, string EntityId, string[] Columns)
        {
            string functionName = GetType().ToString() + " GetSingleData: ";
            Logger.Write(functionName + "Enter", "Trace", 3);
            Logger.Write(functionName + "Parameter: EntityName: " + EntityName, "Trace", 1);
            Logger.Write(functionName + "Parameter: EntityId  : " + EntityId.ToString(), "Trace", 1);
            try
            {
                if (Columns != null && Columns.Length > 0)
                {
                    ColumnSet columnSet = new ColumnSet();
                    columnSet.Attributes = Columns;
                    return CRMService.Retrieve(EntityName, new Guid(EntityId), columnSet);
                }
                else
                    return CRMService.Retrieve(EntityName, new Guid(EntityId), new AllColumns());
            }
            catch (SoapException sEx)
            {
                if (sEx.Detail != null)
                    Logger.Write(functionName + "Error: " + sEx.Detail.InnerText.ToString(), "Error", 5, 999, TraceEventType.Error);
                else
                    Logger.Write(functionName + "Error: " + sEx.ToString(), "Error", 5, 999, TraceEventType.Error);
                throw;
            }
            catch (Exception ex)
            {
                Logger.Write(functionName + "Error: " + ex.ToString(), "Error", 5, 999, TraceEventType.Error);
                throw;
            }
            finally
            {
                Logger.Write(functionName + "Exit", "Trace", 3);
            }
        }

        public BusinessEntity[] GetEntitiesByAttributes(string EntityName, string[] attributes, string[] values)
        {
            string functionName = GetType().ToString() + " GetEntitiesByAttributes: ";
            Logger.Write(functionName + "Enter", "Trace", 3);
            try
            {
                QueryByAttribute query = new QueryByAttribute();
                query.ColumnSet = new AllColumns();
                query.EntityName = EntityName;
                query.Attributes = attributes;
                query.Values = values;
                BusinessEntityCollection queryResult = CRMService.RetrieveMultiple(query);
                if (queryResult.BusinessEntities != null && queryResult.BusinessEntities.Length > 0)
                    return queryResult.BusinessEntities;
                else
                    return null;
            }
            catch (System.Web.Services.Protocols.SoapException sEx)
            {
                if (sEx.Detail != null)
                    Logger.Write(functionName + "Soap Error: " + sEx.ToString(), "Error", 5, 999, TraceEventType.Error);
                else
                    Logger.Write(functionName + "Error: " + sEx.ToString(), "Error", 5, 999, TraceEventType.Error);
                throw;
            }
            catch (Exception ex)
            {
                Logger.Write(functionName + "Error: " + ex.ToString(), "Error", 5, 999, TraceEventType.Error);
                throw;
            }
            finally
            {
                Logger.Write(functionName + "Exit", "Trace", 3);
            }
        }
                
        public contact GetContactFromEmailAddress(string EmailAddress)
        {
            string functionName = GetType().ToString() + " GetContactFromEmailAddress: ";
            Logger.Write(functionName + "Enter", "Trace", 3);
            try
            {

                QueryExpression query = new QueryExpression();

                query.EntityName = "contact";
                query.ColumnSet = new AllColumns();

                query.Criteria = new FilterExpression();
                query.Criteria.FilterOperator = LogicalOperator.Or;

                ConditionExpression condition1 = new ConditionExpression();
                condition1.AttributeName = "emailaddress1";
                condition1.Operator = ConditionOperator.Equal;
                condition1.Values = new object[] { EmailAddress };

                ConditionExpression condition2 = new ConditionExpression();
                condition2.AttributeName = "emailaddress2";
                condition2.Operator = ConditionOperator.Equal;
                condition2.Values = new object[] { EmailAddress };

                ConditionExpression condition3 = new ConditionExpression();
                condition3.AttributeName = "emailaddress3";
                condition3.Operator = ConditionOperator.Equal;
                condition3.Values = new object[] { EmailAddress };

                query.Criteria.Conditions = new ConditionExpression[] { condition1, condition2, condition3 };

                BusinessEntityCollection entities = CRMService.RetrieveMultiple(query);
                if (entities.BusinessEntities.Length > 1)
                    Logger.Write(functionName + "Duplicates detected. More than one contact was found with the e-mail address: " + EmailAddress, "Trace", 3);

                if (entities.BusinessEntities.Length > 0)
                    return (contact)entities.BusinessEntities[0];
                else
                    return null;
            }
            catch (Exception ex)
            {
                Logger.Write(functionName + "Error: " + ex.ToString(), "Error", 5, 999, TraceEventType.Error);
                throw;
            }
            finally
            {
                Logger.Write(functionName + "Exit", "Trace", 3);
            }
        }

        public Guid CreateTask(string Subject, string RegardingId, string RegardingType, string RegardingName, int DueInDays)
        {
            string functionName = GetType().ToString() + " CreateTask: ";
            Logger.Write(functionName + "Enter", "Trace", 3);

            try
            {
                task Task = new task();
                Task.subject = FormatEntityAttributeValue(EntityName.task.ToString(), "subject", Subject);
                Lookup regarding = new Lookup();
                regarding.Value = new Guid(RegardingId);
                regarding.name = RegardingName;
                regarding.type = RegardingType;
                Task.regardingobjectid = regarding;
                Crm.CrmService.CrmDateTime dueDate = new Crm.CrmService.CrmDateTime();
                dueDate.Value = DateTime.Now.AddDays(DueInDays).ToString();
                Task.scheduledend = dueDate;
                Task.scheduledend.Value = dueDate.Value;

                return CallCRMMethod(Task, TypeOfCRMRequest.Create);
            }
            catch (System.Web.Services.Protocols.SoapException sEx)
            {
                if (sEx.Detail != null)
                    Logger.Write(functionName + "Soap Error: " + sEx.ToString(), "Error", 5, 999, TraceEventType.Error);
                else
                    Logger.Write(functionName + "Error: " + sEx.ToString(), "Error", 5, 999, TraceEventType.Error);
                throw;
            }
            catch (Exception ex)
            {
                Logger.Write(functionName + "Error: " + ex.ToString(), "Error", 5, 999, TraceEventType.Error);
                throw;
            }
            finally
            {
                Logger.Write(functionName + "Exit", "Trace", 3);
            }
        }

        public Guid CreateAttachment(string emailId, byte[] attachmentContent, string attachmentName,  string attachmentContentType, int attachmentNumber)
        {
            string functionName = GetType().ToString() + " CreateCRMAttachment: ";
            Logger.Write(functionName + "Enter", "Trace", 3);
            try
            {
                activitymimeattachment newAttachment = new activitymimeattachment();
                newAttachment.activityid = new Lookup();
                newAttachment.activityid.Value = new Guid(emailId);
                newAttachment.activityid.type = EntityName.email.ToString();
                newAttachment.filename = FormatEntityAttributeValue(EntityName.activitymimeattachment.ToString(), "filename", attachmentName);
                newAttachment.attachmentnumber = new Crm.CrmService.CrmNumber();
                newAttachment.attachmentnumber.Value = attachmentNumber;
                string encodedData = System.Convert.ToBase64String(attachmentContent);
                newAttachment.body = encodedData;
                newAttachment.mimetype = attachmentContentType;
                return CallCRMMethod(newAttachment, TypeOfCRMRequest.Create);
            }
            catch (System.Web.Services.Protocols.SoapException sEx)
            {
                if (sEx.Detail != null)
                    Logger.Write(functionName + "Warning: Add Attachment error: " + sEx.Detail.InnerXml.ToString(), "Warning", 4, 999, TraceEventType.Warning);
                else
                    Logger.Write(functionName + "Warning: Add Attachment error: " + sEx.Detail.InnerXml.ToString(), "Warning", 4, 999, TraceEventType.Warning);
                throw;
            }
            catch (Exception ex)
            {
                Logger.Write(functionName + "Warning: Add Attachment error: " + ex.ToString(), "Warning", 4, 999, TraceEventType.Warning);
                throw;
            }
            finally
            {
                Logger.Write(functionName + "Exit", "Trace", 3);
            }
        }

        public Guid CreateEmail(string subject, string body, string fromName, string fromEmailAddress, Dictionary<string, string> toEntities, bool bodyTypeIsText, string headers, string ownerType, string ownerId)
        {
            string functionName = GetType().ToString() + " CreateCRMEmail: ";
            Logger.Write(functionName + "Enter", "Trace", 3);
            try
            {
                Logger.Write(functionName + "Creating 'from' entity", "Trace", 1);
                activityparty from = new activityparty();
                from.addressused = FormatEntityAttributeValue(EntityName.activityparty.ToString(), "addressused",  StripPunctuation(fromName) + "<" + fromEmailAddress + ">");

                Logger.Write(functionName + "Creating email object", "Trace", 1);
                email email = new email();
                Logger.Write(functionName + "Setting from", "Trace", 1);
                email.from = new activityparty[] { from };

                Logger.Write(functionName + "Setting actualend value", "Trace", 1);
                email.actualend = new Crm.CrmService.CrmDateTime();
                email.actualend.Value = DateTime.Now.ToString("s");

                Logger.Write(functionName + "Setting to", "Trace", 1);
                List<activityparty> toAddresses = new List<activityparty>();
                foreach (string key in toEntities.Keys)
                {
                    activityparty to = new activityparty();
                    to.partyid = new Lookup();
                    to.partyid.Value = new Guid(key);
                    to.partyid.type = toEntities[key];
                    to.participationtypemask = new Picklist();
                    to.participationtypemask.Value = 10; // resource
                    toAddresses.Add(to);
                }
                email.to = toAddresses.ToArray();

                Logger.Write(functionName + "Setting subject", "Trace", 1);
                if (string.IsNullOrEmpty(subject))
                    email.subject = "(Untitled Message)";
                else
                    email.subject = FormatEntityAttributeValue(EntityName.email.ToString(), "subject",  subject);

                Logger.Write(functionName + "Setting headers", "Trace", 1);
                //email.new_headers = FormatEntityAttributeValue(EntityName.email.ToString(), "new_headers", headers);

                Logger.Write(functionName + "Setting description", "Trace", 1);
                if (!string.IsNullOrEmpty(body))
                {
                    if (bodyTypeIsText)
                        body = body.Replace("<", "&lt;").Replace(">", "&gt;").Replace("\"", "&quot;").Replace(Environment.NewLine, "<br />");
                    email.description = FormatEntityAttributeValue(EntityName.email.ToString(), "description", body);
                }
                else
                    email.description = string.Empty;

                Logger.Write(functionName + "Setting directioncode", "Trace", 1);
                email.directioncode = new Crm.CrmService.CrmBoolean();
                email.directioncode.Value = false; //incoming

                Logger.Write(functionName + "Setting owner", "Trace", 1);
                email.ownerid = new Owner();
                email.ownerid.type = ownerType;
                email.ownerid.Value = new Guid(ownerId);
                return CallCRMMethod(email, TypeOfCRMRequest.Create);
            }
            catch (Exception ex)
            {
                Logger.Write(functionName + "Error: " + ex.ToString(), "Error", 5, 999, TraceEventType.Error);
                throw;
            }
            finally
            {
                Logger.Write(functionName + "Exit", "Trace", 3);
            }
        }
                
        #region "Calling of various CRM web service methods"

        public Crm.CrmService.Response CallCRMExecuteMethod(object methodObject)
        {
            string functionName = GetType().ToString() + " CallCRMExecuteMethod: ";
            Logger.Write(functionName + "Enter", "Trace", 3);

            _errorRetryCount = 0;

        RetryCreate:
            try
            {
                Logger.Write(functionName + "Attempting method execute", "Trace", 1);
                return CRMService.Execute((Crm.CrmService.Request)methodObject);
            }
            catch (Exception ex)
            {
                if (ErrorIsRecoverable(ex))
                    goto RetryCreate;
                else
                    throw;
            }
            finally
            {
                Logger.Write(functionName + "Exit", "Trace", 3);
            }
        }

        public void CallCRMDelete(string EntityName, Guid EntityId)
        {
            string functionName = GetType().ToString() + " CallCRMDelete: ";
            Logger.Write(functionName + "Enter", "Trace", 3);

            _errorRetryCount = 0;

        RetryDelete:
            try
            {
                Logger.Write(functionName + "Attempting delete", "Trace", 1);
                CRMService.Delete(EntityName, EntityId);
            }
            catch (Exception ex)
            {
                if (ErrorIsRecoverable(ex))
                    goto RetryDelete;
                else
                    throw;
            }
            finally
            {
                Logger.Write(functionName + "Exit", "Trace", 3);
            }
        }

        public Guid CallCRMMethod(object methodObject, TypeOfCRMRequest typeOfCRMRequest)
        {
            string functionName = GetType().ToString() + " CallCRMMethod: ";
            Logger.Write(functionName + "Enter", "Trace", 3);

            _errorRetryCount = 0;

        RetryCreate:
            try
            {
                Logger.Write(functionName + "Attempting method execute", "Trace", 1);
                switch (typeOfCRMRequest)
                {
                    case TypeOfCRMRequest.Update:
                        CRMService.Update((BusinessEntity)methodObject);
                        break;
                    case TypeOfCRMRequest.Create:
                        return CRMService.Create((BusinessEntity)methodObject);
                    case TypeOfCRMRequest.Execute:
                        Crm.CrmService.Response response = CRMService.Execute((Crm.CrmService.Request)methodObject);
                        if (response is CreateResponse)
                            return ((CreateResponse)response).id;
                        break;
                    default:
                        break;
                }
                return new Guid();
            }
            catch (Exception ex)
            {
                if (ErrorIsRecoverable(ex))
                    goto RetryCreate;
                else
                    throw;
            }
            finally
            {
                Logger.Write(functionName + "Exit", "Trace", 3);
            }
        }

        private bool ErrorIsRecoverable(Exception ex)
        {
            string functionName = GetType().ToString() + " HandleServiceErrors: ";
            Logger.Write(functionName + "Enter", "Trace", 3);
            try
            {
                _errorRetryCount++;
                if (ex is SoapException)
                    return RetryCRMSoapException((SoapException)ex);
                else if (ex is WebException)
                    return RetryCRMWebException((WebException)ex);
                else
                {
                    Logger.Write(functionName + "Cannot recover from this error: " + ex.ToString(), "Trace", 3);
                    return false;
                }
            }
            catch (Exception fEx)
            {
                Logger.Write(functionName + "Error: " + fEx.ToString(), "Error", 5, 999, TraceEventType.Error);
                throw;
            }
            finally
            {
                Logger.Write(functionName + "Exit", "Trace", 3);
            }
        }

        private bool RetryCRMSoapException(SoapException cEx)
        {
            string functionName = GetType().ToString() + " RetryCRMSoapException: ";
            Logger.Write(functionName + "Enter", "Trace", 3);

            try
            {
                string errorValue = string.Empty;
                if (cEx.Detail != null)
                    errorValue = cEx.Detail.InnerText.ToString();
                else
                    errorValue = cEx.ToString();
                errorValue = errorValue.ToLower();
                //CRM sometimes comes back with "Generic SQL error." when trying to create an contact.
                //I think this happens because there's a lock on the DB somewhere, so I'm trying to cater for that here:
                if (errorValue.Contains("generic sql error") ||
                    errorValue.Contains("the operation has timed out") ||
                    errorValue.Contains("unable to connect to the remote server"))
                {
                    if (_errorRetryCount < 7)
                    {
                        //force the CRMHelper class to recreate the service:
                        CRMService = null;
                        //Log the error as a warning:
                        Logger.Write(functionName + "Warning: Method execute error: " + errorValue + Environment.NewLine + "Waiting 10 seconds before retrying create: " + _errorRetryCount, "Warning", 4, 999, TraceEventType.Warning);
                        //Sleep a bit before retrying the create
                        System.Threading.Thread.Sleep(10000);
                        return true;
                    }
                    else
                        return false;
                }
                else
                {
                    Logger.Write(functionName + "Warning: Cannot recover from this error: " + errorValue, "Warning", 4, 999, TraceEventType.Warning);
                    return false;
                }
            }
            catch (Exception ex)
            {
                Logger.Write(functionName + "Error: " + ex.ToString(), "Error", 5, 999, TraceEventType.Error);
                throw;
            }
            finally
            {
                Logger.Write(functionName + "Exit", "Trace", 3);
            }
        }

        private bool RetryCRMWebException(WebException wEx)
        {
            string functionName = GetType().ToString() + " RetryCRMWebException: ";
            Logger.Write(functionName + "Enter", "Trace", 3);

            try
            {
                string errorValue = wEx.ToString();
                //Sometimes there aren't enough sockets to go around. 
                //Sockets clear themselves after 150 seconds, so we're just going to hang around a bit and then try again.
                if (errorValue.ToLower().Contains("unable to connect to the remote server") ||
                    errorValue.ToLower().Contains("the operation has timed out") ||
                    errorValue.ToLower().Contains("the underlying connection was closed") ||
                    errorValue.ToLower().Contains("the request failed with http status 401: unauthorized."))
                {
                    if (_errorRetryCount < 7)
                    {
                        //force the CRMHelper class to recreate the service:
                        CRMService = null;
                        //Log the error as a warning:
                        Logger.Write(functionName + "Warning: Method execute error: " + errorValue + Environment.NewLine + "Waiting 90 seconds before retrying create: " + _errorRetryCount, "Warning", 4, 999, TraceEventType.Warning);
                        //Sleep a bit before retrying the create
                        System.Threading.Thread.Sleep(90000);
                        return true;
                    }
                    else
                        return false;
                }
                else
                {
                    Logger.Write(functionName + "Warning: Cannot recover from this error: " + errorValue, "Warning", 4, 999, TraceEventType.Warning);
                    return false;
                }
            }
            catch (Exception ex)
            {
                Logger.Write(functionName + "Error: " + ex.ToString(), "Error", 5, 999, TraceEventType.Error);
                throw;
            }
            finally
            {
                Logger.Write(functionName + "Exit", "Trace", 3);
            }
        }

        #endregion

        #region "Routing"

        public void RouteCase(Guid CaseToRoute, string SourceQueue, string TargetQueue)
        {
            string functionName = GetType().ToString() + " RouteCase: ";
            Logger.Write(functionName + "Enter", "Trace", 3);
            Logger.Write(functionName + "Parameter: SourceQueue: " + SourceQueue, "Trace", 1);
            Logger.Write(functionName + "Parameter: TargetQueue: " + TargetQueue, "Trace", 1);
            try
            {
                RouteRequest routeRequest = new RouteRequest();
                TargetQueuedIncident targetCase = new TargetQueuedIncident();
                targetCase.EntityId = CaseToRoute;
                routeRequest.Target = targetCase;
                routeRequest.EndpointId = new Guid(TargetQueue);
                routeRequest.RouteType = RouteType.Queue;
                routeRequest.SourceQueueId = new Guid(SourceQueue);
                CallCRMMethod(routeRequest, TypeOfCRMRequest.Execute);
            }
            catch (SoapException sEx)
            {
                if (sEx.Detail != null)
                    Logger.Write(functionName + "Error: " + sEx.Detail.InnerText.ToString(), "Error", 5, 999, TraceEventType.Error);
                else
                    Logger.Write(functionName + "Error: " + sEx.ToString(), "Error", 5, 999, TraceEventType.Error);
                throw;
            }
            catch (Exception ex)
            {
                Logger.Write(functionName + "Error: " + ex.ToString(), "Error", 5, 999, TraceEventType.Error);
                throw;
            }
            finally
            {
                Logger.Write(functionName + "Exit", "Trace", 3);
            }
        }

        public void RouteEMail(email EMail, string SourceQueue, string TargetQueue)
        {
            string functionName = GetType().ToString() + " RouteEMail: ";
            Logger.Write(functionName + "Enter", "Trace", 3);
            Logger.Write(functionName + "Parameter: SourceQueue: " + SourceQueue, "Trace", 1);
            Logger.Write(functionName + "Parameter: TargetQueue: " + TargetQueue, "Trace", 1);
            try
            {
                RouteRequest routeRequest = new RouteRequest();
                TargetQueuedEmail objTargetEMail = new TargetQueuedEmail();
                objTargetEMail.EntityId = EMail.activityid.Value;
                routeRequest.Target = objTargetEMail;
                routeRequest.EndpointId = new Guid(TargetQueue);
                routeRequest.RouteType = RouteType.Queue;
                routeRequest.SourceQueueId = new Guid(SourceQueue);
                CallCRMMethod(routeRequest, TypeOfCRMRequest.Execute);
            }
            catch (SoapException sEx)
            {
                if (sEx.Detail != null)
                    Logger.Write(functionName + "Error: " + sEx.Detail.InnerText.ToString(), "Error", 5, 999, TraceEventType.Error);
                else
                    Logger.Write(functionName + "Error: " + sEx.ToString(), "Error", 5, 999, TraceEventType.Error);
                throw;
            }
            catch (Exception ex)
            {
                Logger.Write(functionName + "Error: " + ex.ToString(), "Error", 5, 999, TraceEventType.Error);
                throw;
            }
            finally
            {
                Logger.Write(functionName + "Exit", "Trace", 3);
            }
        }

        public void RouteTaskToQueue(Guid TaskId, Guid TargetQueueId)
        {
            string functionName = GetType().ToString() + " RouteEmailToQueue: ";
            Logger.Write(functionName + "Enter", "Trace", 3);
            try
            {
                QueryByAttribute query = new QueryByAttribute();
                query.ColumnSet = new AllColumns();
                query.EntityName = EntityName.queueitem.ToString();
                query.Attributes = new string[] { "objectid" };
                query.Values = new string[] { TaskId.ToString() };
                BusinessEntityCollection retrieved = CRMService.RetrieveMultiple(query);
                if (retrieved.BusinessEntities.Length == 0)
                    throw new Exception("Could not retrieve current queue for email being routed from Exchange: " + TaskId);
                queueitem qitem = (queueitem)retrieved.BusinessEntities.GetValue(0);
                TargetQueuedTask tqt = new TargetQueuedTask();
                tqt.EntityId = TaskId;
                RouteRequest route = new RouteRequest();
                route.Target = tqt;
                route.RouteType = RouteType.Queue;
                route.SourceQueueId = qitem.queueid.Value;
                route.EndpointId = TargetQueueId;
                CallCRMMethod(route, TypeOfCRMRequest.Execute);
            }
            catch (SoapException sEx)
            {
                if (sEx.Detail != null)
                    Logger.Write(functionName + "Error: " + sEx.Detail.InnerText.ToString(), "Error", 5, 999, TraceEventType.Error);
                else
                    Logger.Write(functionName + "Error: " + sEx.ToString(), "Error", 5, 999, TraceEventType.Error);
                throw;
            }
            catch (Exception ex)
            {
                Logger.Write(functionName + "Error: " + ex.ToString(), "Error", 5, 999, TraceEventType.Error);
                throw;
            }
            finally
            {
                Logger.Write(functionName + "Exit", "Trace", 3);
            }
        }

        public void RouteEmailToQueue(Guid EmailId, Guid TargetQueueId)
        {
            string functionName = GetType().ToString() + " RouteEmailToQueue: ";
            Logger.Write(functionName + "Enter", "Trace", 3);
            try
            {
                QueryByAttribute query = new QueryByAttribute();
                query.ColumnSet = new AllColumns();
                query.EntityName = EntityName.queueitem.ToString();
                query.Attributes = new string[] { "objectid" };
                query.Values = new string[] { EmailId.ToString() };
                BusinessEntityCollection retrieved = CRMService.RetrieveMultiple(query);
                if (retrieved.BusinessEntities.Length == 0)
                    throw new Exception("Could not retrieve current queue for email being routed from Exchange: " + EmailId);
                queueitem qitem = (queueitem)retrieved.BusinessEntities.GetValue(0);
                TargetQueuedEmail tqt = new TargetQueuedEmail();
                tqt.EntityId = EmailId;
                RouteRequest route = new RouteRequest();
                route.Target = tqt;
                route.RouteType = RouteType.Queue;
                route.SourceQueueId = qitem.queueid.Value;
                route.EndpointId = TargetQueueId;
                CallCRMMethod(route, TypeOfCRMRequest.Execute);
            }
            catch (SoapException sEx)
            {
                if (sEx.Detail != null)
                    Logger.Write(functionName + "Error: " + sEx.Detail.InnerText.ToString(), "Error", 5, 999, TraceEventType.Error);
                else
                    Logger.Write(functionName + "Error: " + sEx.ToString(), "Error", 5, 999, TraceEventType.Error);
                throw;
            }
            catch (Exception ex)
            {
                Logger.Write(functionName + "Error: " + ex.ToString(), "Error", 5, 999, TraceEventType.Error);
                throw;
            }
            finally
            {
                Logger.Write(functionName + "Exit", "Trace", 3);
            }
        }
        
        #endregion

        #region "Utility"

        public string FormatEntityAttributeValue(string EntityName, string AttributeName, string value)
        {
            if (value == null)
                return string.Empty;

            AttributeMetadata attributeMetadata = GetAttribute(EntityName, AttributeName);
            int maxLength = 0;
            switch (attributeMetadata.AttributeType.Value)
            {
                case AttributeType.Memo:
                    maxLength = ((MemoAttributeMetadata)attributeMetadata).MaxLength.Value;
                    break;
                case AttributeType.String:
                    maxLength = ((StringAttributeMetadata)attributeMetadata).MaxLength.Value;
                    break;
                default:
                    break;
            }
            if (value.Length > maxLength)
                return value.Substring(0, maxLength - 2);
            else
                return value;
        }

        private static string StripPunctuation(string stringToStrip)
        {
            //Remove any punctuation from address name email name
            StringBuilder strippedString = new StringBuilder(stringToStrip.Length);
            foreach (char curChar in stringToStrip)
                if (!Char.IsPunctuation(curChar))
                    strippedString.Append(curChar);
            return strippedString.ToString();
        }

        #endregion

    }
}
